from .attributes import *

import numpy


class DiamondSquare(Object):
    scatter = Attribute(initial = 0)
    
    array  = Attribute(initiable = False)    
    stages = ListAttribute(initiable = False)
    queue  = ListAttribute(initiable = False)
    
    def value(self, x, y):
        return self._array[x, y]
    
    def set_value(self, x, y, value):
        self._array[x, y] = max(min(value, 1), 0)
    
    def random_amount(self, n):
        return random.random() * self.scatter * 2 - self.scatter
    
    def initial_value(self, n, x, y):
        return self._stages[n](x, y)
    
    def add_stages(self, n, f):
        for _ in range(n):
            self._stages.append(f)
    
    @property
    def num_preset_steps(self):
        return len(self._stages)
    
    def add_to_queue(self, *args):
        self._queue.append(args)
    
    def pop_queue(self):
        return self._queue.pop()
    
    @property
    def queue_is_empty(self):
        return len(self._queue) == 0
    
    def create_value(self, n, point, sources):
        if n < self.num_preset_steps:
            self.set_value(point[0], point[1], self.initial_value(n, *point))
        else:
            value = sum(self.value(*each) for each in sources) / len(sources) + self.random_amount(n)
            
            self.set_value(point[0], point[1], value)
    
    def first_step(self, x1, y1, x2, y2):
        self.create_value(0, (x1, y1), ())
        self.create_value(0, (x1, y2), ())
        self.create_value(0, (x2, y1), ())
        self.create_value(0, (x2, y2), ())
        
        self.add_to_queue(1, x1, y1, x2, y2)
    
    def diamond_step(self, n, x1, y1, x2, y2):
        self.create_value(n, ((x1 + x2) // 2, (y1 + y2) // 2),
                          ((x1, y1), (x1, y2), (x2, y1), (x2, y2)))
    
    def square_step(self, n, x1, y1, x2, y2):
        self.create_value(n, ((x1 + x2) // 2, y1), ((x1, y1), (x2, y1)))
        self.create_value(n, (x1, (y1 + y2) // 2), ((x1, y1), (x1, y2)))
        
        if x2 == self.array.shape[0] - 1:
            self.create_value(n, (x2, (y1 + y2) // 2), ((x2, y1), (x2, y2)))
        
        if y2 == self.array.shape[1] - 1:
            self.create_value(n, ((x1 + x2) // 2, y2), ((x1, y2), (x2, y2)))
    
    def step(self, n, x1, y1, x2, y2):
        if x1 >= x2 or y1 >= y2:
            return
        
        self.diamond_step(n, x1, y1, x2, y2)
        self.square_step (n, x1, y1, x2, y2)
        
        x3 = (x1 + x2) // 2
        y3 = (y1 + y2) // 2
        
        if x1 == x3 or x2 == x3 or y1 == y3 or y2 == y3:
            return
        
        self.add_to_queue(n + 1, x1, y1, x3, y3)
        self.add_to_queue(n + 1, x3, y1, x2, y3)
        self.add_to_queue(n + 1, x1, y3, x3, y2)
        self.add_to_queue(n + 1, x3, y3, x2, y2)
    
    def generate(self, width, height):
        self.array = numpy.zeros([width, height])
        
        self.first_step(0, 0, width - 1, height - 1)
        
        while not self.queue_is_empty:
            self.step(*self.pop_queue())
        
        return self.array
